package com.tianji.promotion.service.impl;

import cn.hutool.core.bean.copier.CopyOptions;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.domain.dto.UserCouponDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.ICouponService;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.utils.CodeUtil;
import com.tianji.promotion.utils.MyLock;
import com.tianji.promotion.utils.RedisLock;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {

    private final CouponMapper couponMapper;

    private final IExchangeCodeService exchangeCodeService;

    private final StringRedisTemplate redisTemplate;

    private final RabbitMqHelper mqHelper;

    /**
     * 领取优惠券
     */
    /*@Override
    public void receiveCoupon(Long id) {
        //1.查询优惠券
        if (id == null) {
            throw new BadRequestException("非法参数");
        }
        Coupon coupon = couponMapper.selectById(id);
        //2.各种校验
        //2.1 优惠券是否存在
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }
        //2.2 优惠券是否为发放状态
        if (!coupon.getStatus().equals(CouponStatus.ISSUING)) {
            throw new BadRequestException("不能领取非发放中的优惠券");
        }
        //2.3 是否开放或过期
        LocalDateTime now = LocalDateTime.now();
        if (now.isBefore(coupon.getIssueBeginTime()) || now.isAfter(coupon.getIssueEndTime())) {
            throw new BadRequestException("优惠券未发放或已过期");
        }
        //2.4 检查库存
        if (coupon.getTotalNum() <= 0 || coupon.getIssueNum() >= coupon.getTotalNum()) {
            throw new BadRequestException("优惠券库存不足");
        }
        //2.5 检查超领并生成用户券
        Long userId = UserContext.getUser();
        IUserCouponService userCouponService = (IUserCouponService) AopContext.currentProxy();
        userCouponService.checkAndCreateUserCoupon(userId, coupon, null);
    }*/

    /**
     * redis版-领取优惠券之校验
     */
    @Override
    @MyLock(name = "lock:coupon:#{userId}")
    public void receiveCoupon(Long id) {
        //1.查询优惠券
        if (id == null) {
            throw new BadRequestException("非法参数");
        }
        Coupon coupon = this.queryCouponByCache(id);
        //2.各种校验
        //2.1优惠券是否存在
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }
        //2.2.是否开放或过期
        LocalDateTime now = LocalDateTime.now();
        if (now.isBefore(coupon.getIssueBeginTime()) || now.isAfter(coupon.getIssueEndTime())) {
            throw new BadRequestException("优惠券未发放或已过期");
        }
        //2.3.检查库存
        if (coupon.getTotalNum() <= 0) {
            throw new BadRequestException("优惠券库存不足");
        }
        //2.4.添加用户券到redis，用于统计数量
        Long userId = UserContext.getUser();
        String userCouponKey = PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + id;
        //自增加1
        Long increment = redisTemplate.opsForHash().increment(userCouponKey, userId.toString(), 1);
        //判断是否超出限领数量
        if (increment > coupon.getUserLimit()) {
            throw new BadRequestException("超出限领数量");
        }
        //2.5.优惠券库存-1
        String couponKey = PromotionConstants.COUPON_CACHE_KEY_PREFIX + id;
        redisTemplate.opsForHash().increment(couponKey, "totalNum", -1);
        //2.6.发送消息
        UserCouponDTO msg = new UserCouponDTO();
        msg.setCouponId(id);
        msg.setUserId(userId);
        mqHelper.send(MqConstants.Exchange.PROMOTION_EXCHANGE,
                MqConstants.Key.COUPON_RECEIVE,
                msg);
    }

    /**
     * 从缓存中获取coupon
     */
    private Coupon queryCouponByCache(Long id) {
        String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + id;
        //一次性获取所有hash
        Map<Object, Object> map = redisTemplate.opsForHash().entries(key);
        //赋值属性
        return BeanUtils.mapToBean(map, Coupon.class, false, CopyOptions.create());
    }

    /**
     * 检查超领并生成用户券
     */
    @MyLock(name = "lock:coupon:#{userId}")
    @Transactional
    public void checkAndCreateUserCoupon(Long userId, Coupon coupon, Long serialNum) {
        //1.查询用户领该券的数量
        Integer count = this.lambdaQuery().eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getCouponId, coupon.getId())
                .count();
        if (coupon != null && count >= coupon.getUserLimit()) {
            throw new BadRequestException("已达到领取上限");
        }
        //2.发放优惠券自增
        int r = couponMapper.incrIssueNum(coupon.getId());
        if (r == 0) {
            throw new BizIllegalException("优惠券库存不足");
        }
        //3.保存到用户券表
        saveUserCoupon(userId, coupon);
        //4.更新兑换码状态为已使用
        if (serialNum != null) {
            exchangeCodeService.lambdaUpdate()
                    .set(ExchangeCode::getStatus, UserCouponStatus.USED)
                    .set(ExchangeCode::getUserId, userId)
                    .eq(ExchangeCode::getId, serialNum)
                    .update();
        }
//        throw new RuntimeException("故意抛错");
    }

    /**
     * 检查并生成优惠券-Redis版
     */
    @Override
    public void checkAndCreateUserCouponNew(UserCouponDTO msg) {
        Coupon coupon = couponMapper.selectById(msg.getCouponId());
        if (coupon == null) {
            return;
        }
        //自增
        int num = couponMapper.incrIssueNum(coupon.getId());
        //优惠券库存是否足够
        if (num == 0) {
            return;
        }
        //生成用户券
        saveUserCoupon(msg.getUserId(), coupon);
    }

    /**
     * 兑换码兑换优惠券
     */
    @Override
    @Transactional
    public void exchangeCoupon(String code) {
        //1.code非空
        if (StringUtils.isBlank(code)) {
            throw new BadRequestException("兑换码不能为空");
        }
        //2.解析code得到自增id
        Long serialNum = CodeUtil.parseCode(code);
        //3.是否已兑换
        boolean result = exchangeCodeService.updateExchangeCodeMark(serialNum, true);
        if (result) {
            throw new BizIllegalException("兑换码已经兑换过了");
        }
        try {
            //4.兑换码是否存在
            ExchangeCode exchangeCode = exchangeCodeService.getById(serialNum);
            if (exchangeCode == null) {
                throw new BizIllegalException("兑换码不存在");
            }
            //5.是否过期
            LocalDateTime now = LocalDateTime.now();
            if (now.isAfter(exchangeCode.getExpiredTime())) {
                throw new BizIllegalException("兑换码已过期");
            }
            //6.是否超领 -> 优惠券发放数量+1 -> 生成用户券 -> 更新兑换码状态
            Long userId = UserContext.getUser();
            Coupon coupon = couponMapper.selectById(exchangeCode.getExchangeTargetId());
            if (coupon == null) {
                throw new BizIllegalException("优惠券不存在");
            }
            checkAndCreateUserCoupon(userId, coupon, serialNum);
        } catch (Exception e) {
            exchangeCodeService.updateExchangeCodeMark(serialNum, false);
            throw e;
        }

    }

    /**
     * 保存到用户券表
     */
    private void saveUserCoupon(Long userId, Coupon coupon) {
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setCouponId(coupon.getId());
        userCoupon.setUserId(userId);
        // 俩时间可能为空
        LocalDateTime termBeginTime = coupon.getTermBeginTime();
        LocalDateTime termEndTime = coupon.getTermEndTime();
        LocalDateTime now = LocalDateTime.now();
        if (termBeginTime == null && termEndTime == null) {
            termBeginTime = now;
            termEndTime = termBeginTime.plusDays(coupon.getTermDays());
        }
        userCoupon.setTermBeginTime(termBeginTime);
        userCoupon.setTermEndTime(termEndTime);
        this.save(userCoupon);
    }

}
